#include "Strassen_task.h"

// Implementation of the class functions //

/* M1 Task */
M1_Task::M1_Task(float* a11,float* a22, float* b11, float* b22,float* M1, int half,TState* pState, WorkQueue * Q) {
  _a11 = a11; _a22 = a22; _b11 = b11; _b22 = b22; _M1 = M1;   
  _half = half;
  atemp = (float*)malloc(half*half*sizeof(float));// use new
  btemp = (float*)malloc(half*half*sizeof(float));
  _pState = pState;
  _Q = Q;
  //doneState = new TState();
  //*doneState = Create;
}

M1_Task::~M1_Task() {
  //free(atemp);
  //free(btemp);
}

void M1_Task::run() {
  //printf("----> M1 Task: M1 Task started \n");
  AddMatrix(_a11,_a22,atemp,_half);
  AddMatrix(_b11,_b22,btemp,_half);
  //printf("----> M1 Task: M1 Task finished operating\n");
  Strassen * S = new Strassen(atemp, btemp, _M1, _half, _Q, _pState, 1);
  _Q->addTask(S);
  *doneState = Done;
  //printf("----> M1 Task: M1 Task spawned new Strassen Task and done\n");
  // Strassen(atemp, btemp, _M1,_half);
}

void M1_Task::print() {
	printf("I am M1 task printing\n");
}
/* End of M1_Task */

/* M2 Task */
M2_Task::M2_Task(float* a21,float* a22, float* b11,float* M2, int half,TState* pState, WorkQueue * Q) {
  _a21 = a21; _a22 = a22; _b11 = b11; _M2 = M2;   
  _half = half;
  atemp = (float*)malloc(_half*_half*sizeof(float));
  btemp = (float*)malloc(_half*_half*sizeof(float));
  _pState = pState;
  _Q = Q;
  //doneState = new TState();
  //*doneState = Create;
}

M2_Task::~M2_Task() {
  //free(atemp);
  //free(btemp);
}

void M2_Task::run() {
  //printf("----> M2 Task: M2 Task started \n");
  AddMatrix(_a21, _a22, atemp, _half);
  Strassen * S = new Strassen(atemp,_b11,_M2,_half,_Q,_pState, 1);
  _Q->addTask(S);
  *doneState = Done; 
  //printf("----> M2 Task: M2 Task spawned new Strassen Task and done\n");
  //Strassen(atemp, _b11, _M2, _half);
}

void M2_Task::print() {
	printf("I am M2 task printing\n");
}
/* End of M2 Task */

/* M3 Task */
M3_Task::M3_Task(float* a11, float* b12, float* b21,float *M3, int half,TState* pState,  WorkQueue * Q) {
  _a11 = a11; _b12 = b12; _b21 = b21;
  _half = half;
  _M3 = M3;
  atemp = (float*)malloc(half*half*sizeof(float));
  _pState = pState;
  _Q = Q;
  //doneState = new TState();
  //*doneState = Create;
}

M3_Task::~M3_Task() {
  //free(atemp);
}

void M3_Task::run() {
  //printf("----> M3 Task: M3 Task started \n");
  SubMatrix(_b12, _b21, atemp, _half);
  Strassen * S = new Strassen(_a11,atemp,_M3,_half,_Q,_pState, 1);
  _Q->addTask(S);
  *doneState = Done;
  //printf("----> M3 Task: M3 Task spawned new Strassen Task and done\n");
  //Strassen(_a11, atemp, _M3, _half);
}

void M3_Task::print() {
	printf("I am M3 task printing\n");
}
/* End of M3 Task */

/* M4 Task */
M4_Task::M4_Task(float* a22, float* b21, float* b11, float* M4, int half,TState* pState, WorkQueue * Q) {
  _a22 = a22; _b21 = b21; _b11 = b11; _half = half;_M4 = M4;
  atemp = (float*)malloc(_half*_half*sizeof(float));
  _pState = pState;
  _Q = Q;
  //doneState = new TState();
  //*doneState = Create;
}

M4_Task::~M4_Task() {
  //free(atemp);
}

void M4_Task::run() {
 //printf("----> M4 Task: M4 Task started \n");
  SubMatrix(_b21, _b11, atemp, _half);
  Strassen * S = new Strassen(_a22,atemp,_M4,_half,_Q,_pState, 1);
  _Q->addTask(S);
  *doneState = Done;
  //printf("----> M4 Task: M4 Task spawned new Strassen Task and done\n");
}

void M4_Task::print() {
	printf("I am M4 task printing\n");
}
/* End of M4 Task */

/* M5 Task */
M5_Task::M5_Task(float* a11, float* a12, float* b22, float* M5, int half,TState* pState, WorkQueue * Q) {
  _a11 = a11; _a12 = a12; _b22 = b22; _half = half;_M5 = M5;
  atemp = (float*)malloc(_half*_half*sizeof(float));
  _pState = pState;
  _Q = Q;
  //doneState = new TState();
  //*doneState = Create;
}

M5_Task::~M5_Task() {
  //free(atemp);
}

void M5_Task::run() {
  //printf("----> M5 Task: M5 Task started \n");
  AddMatrix(_a11,_a12,atemp,_half);
  Strassen * S = new Strassen(_b22,atemp,_M5,_half,_Q,_pState, 1);
  _Q->addTask(S);
  *doneState = Done;
  //printf("----> M5 Task: M5 Task spawned new Strassen Task and done\n");
}

void M5_Task::print() {
	printf("I am M5 task printing\n");
}
/* End M5 Task */

/* M6 Task */
M6_Task::M6_Task(float* a21,float* a11, float* b11, float* b12,float* M6, int half,TState* pState,  WorkQueue * Q) {
  _a21 = a21; _a11 = a11; _b11 = b11; _b12 = b12; _M6 = M6;   
  _half = half;
  atemp = (float*)malloc(half*half*sizeof(float));
  btemp = (float*)malloc(half*half*sizeof(float));
  _pState = pState;
  _Q = Q;
  //doneState = new TState();
  //*doneState = Create;
}

M6_Task::~M6_Task() {
  //free(atemp);
  //free(btemp);
}

void M6_Task::run() {
 //printf("----> M6 Task: M6 Task started \n");
  SubMatrix(_a21,_a11,atemp,_half);
  AddMatrix(_b11,_b12,btemp,_half);
  Strassen * S = new Strassen(atemp,btemp,_M6,_half,_Q,_pState, 1);
  _Q->addTask(S);
  *doneState = Done;
  //printf("----> M6 Task: M6 Task spawned new Strassen Task and done\n");
}

void M6_Task::print() {
	printf("I am M6 task printing\n");
}
/* End of M6 Task */

/* M7 Task */
M7_Task::M7_Task(float* a12,float* a22, float* b21, float* b22,float* M7, int half,TState* pState,  WorkQueue * Q) {
  _a12 = a12; _a22 = a22; _b21 = b21; _b22 = b22; _M7 = M7;   
  _half = half;
  atemp = (float*)malloc(half*half*sizeof(float));
  btemp = (float*)malloc(half*half*sizeof(float));
  _pState = pState;
  _Q = Q;
  //doneState = new TState();
  //*doneState = Create;
}

M7_Task::~M7_Task() {
  //free(atemp);
  //free(btemp);
}

void M7_Task::run() {
 //printf("----> M7 Task: M7 Task started \n");
  SubMatrix(_a12,_a22,atemp,_half);
  AddMatrix(_b21,_b22,btemp,_half);
  Strassen * S = new Strassen(atemp,btemp,_M7,_half,_Q,_pState, 1);
  _Q->addTask(S);
  *doneState = Done;
  
  //printf("----> M7 Task: M7 Task spawned new Strassen Task and done\n");
}

void M7_Task::print() {
	printf("I am M7 task printing\n");
}
/* End of M7 Task */

/* Strassen Task */
Strassen::Strassen(float* a, float *b, float* c, int size, WorkQueue *  _Q , TState * pState , int _sid ) {
  myState = new TState[7]; // allocate state variables for children
  for (int i = 0 ; i < 7 ; i++) {
      myState[i] = Create;
  } 
  sid = _sid;
  starting = true;
  mysize = size;
  half = size/2;
  _pState = pState;
  *_pState = Create;
  Q = _Q;
  _a = a;
  _b = b;
  _c = c;
  //doneState = new TState();
  //*doneState = Create;

  // Allocate sub matrices //
  //printf("---> Strassen Task: Starting Allocation \n");
  size_t _size = half*half*sizeof(float);
  a11 = (float*)(malloc(_size));
  a12 = (float*)(malloc(_size));
  a21 = (float*)(malloc(_size));
  a22 = (float*)(malloc(_size));
  b11 = (float*)(malloc(_size));
  b12 = (float*)(malloc(_size));
  b21 = (float*)(malloc(_size));
  b22 = (float*)(malloc(_size));
  c11 = (float*)(malloc(_size));
  c12 = (float*)(malloc(_size));
  c21 = (float*)(malloc(_size));
  c22 = (float*)(malloc(_size));
  M1 = (float*)(malloc(_size));
  M2 = (float*)(malloc(_size));
  M3 = (float*)(malloc(_size));
  M4 = (float*)(malloc(_size));
  M5 = (float*)(malloc(_size));
  M6 = (float*)(malloc(_size));
  M7 = (float*)(malloc(_size));
  atemp = (float*)(malloc(_size));
  btemp = (float*)(malloc(_size));
  //printf("----> Strassen Task: Allocation Done\n");
  // fill in submatrices
  int i , j;
  for(i=0 ; i < half ; i++) {
    for(j = 0 ; j < half ; j++) {
      a11[i*half + j] = a[i*size + j];
      a12[i*half + j] = a[(half+i)*size + j];
      a21[i*half + j] = a[i*size + (half+j)];
      a22[i*half + j] = a[(half+i)*size + (half+j)];
      b11[i*half + j] = b[i*size + j];
      b12[i*half + j] = b[(half+i)*size + j];
      b21[i*half + j] = b[i*size + (half+j)];
      b22[i*half + j] = b[(half+i)*size + (half+j)];
    }
  }
}

Strassen:: ~Strassen() {
  delete[] myState;
}

void Strassen::run() {
  if (starting) {
    if(mysize <= MY_THRESHOLD) {// size is too small simply do trivial multiplication
      //printf("---------> Strassen Task: Reached the limiting point!\n");
      NaiveMul(_a,_b,_c,mysize);
      *_pState = Done;
      *doneState = Done;
      free(a11);
      free(a12);
      free(a21);
      free(a22); 
      free(b11);
      free(b12);
      free(b21);
      free(b22);
      free(c11);
      free(c12);
      free(c21);
      free(c22); 
      free(M1);
      free(M2);
      free(M3);
      free(M4);
      free(M5);
      free(M6);
      free(M7);
      free(atemp);
      free(btemp);
      return;
    }
    // Create the sub tasks that should do the work
    //printf("---> Strassen Task: Now Working\n");
    *_pState = Working;
    M1_Task* T1 = new M1_Task(a11, a22, b11, b22, M1, half,&myState[0],Q);
    Q->addTask(T1);
    //printf("----> Strassen Task: Spawned M1 task \n");
    M2_Task* T2 = new M2_Task(a21,a22,b11,M2,half,&myState[1],Q);
    Q->addTask(T2);
    //printf("----> Strassen Task: Spawned M2 task \n");
    M3_Task* T3 = new M3_Task(a11,b12,b21,M3,half,&myState[2],Q);
    Q->addTask(T3);
    //printf("----> Strassen Task: Spawned M3 task \n");
    M4_Task* T4 = new M4_Task(a22,b21,b11,M4,half,&myState[3],Q);
    Q->addTask(T4);
    //printf("----> Strassen Task: Spawned M4 task \n");
    M5_Task* T5 = new M5_Task(a11,a12,b22,M5,half,&myState[4],Q);
    Q->addTask(T5);
    //printf("----> Strassen Task: Spawned M5 task \n");
    M6_Task* T6 = new M6_Task(a21,a11,b11,b12,M6,half,&myState[5],Q);
    Q->addTask(T6);
    //printf("----> Strassen Task: Spawned M6 task \n");
    M7_Task* T7 = new M7_Task(a12,a22,b21,b22,M7,half,&myState[6],Q);
    Q->addTask(T7);
    //printf("----> Strassen Task: Spawned M7 task \n");
    starting = false;
    //printf("----> Strassen Task: I should now go back to sleep \n");
    //Q->addTask(this);
    //printf("----> Strassen Task: I added myself to the queue by thread %d\n", pthread_self());
    *_pState = Idle;
    if (pthread_yield() != 0) {
      //printf("----> Strassen Task: Thread yeilding error \n");
      //printf("%s", strerror(errno));
      pthread_exit(0);
    }
  }else {// Already started working and the tasks have been created
    //printf("----> Awake by thread %d\n",pthread_self());
    *_pState = Working;
    int i = 0 ;
    bool running = true;
    bool done = true;
    for(; i < 7; i++) {
      if (myState[i] == Create || myState[i] == Idle) {// at least one state is in create or idle
	running = false;
	done = false;
      }else if (myState[i] == Working) {// to set running to true, all states must be working
	running = running && true;
	done = false;
      }else if (myState[i] == Done) {// to set done to true, all states must be true
	done = done && true;
      }
    }
    
    // Now check status and decide what to do for work 
    if (done) {//all tasks are done, bring everything togethere and finish
      /* Now we have the Ms get the Cs */
      //c11 = M1 + M4 - M5 + M7;
      AddMatrix(M1,M4,atemp,half);
      SubMatrix(M7,M5,btemp,half);
      AddMatrix(atemp,btemp,c11,half);
      //printf("---> Strassen Task: Printing c11\n");
      //printMatrix(c11, half);
      
      //c12 = M3 + M5
      AddMatrix(M3,M5,c12,half);
      //printf("---> Strassen Task: Printing c12\n");
      //printMatrix(c12, half);
      
      //c21 = M2 + M4
      AddMatrix(M2,M4,c21,half);
      //printf("---> Strassen Task: Printing c21\n");
      //printMatrix(c21, half);
      
      //c22 = M1 - M2 + M3 + M6
      SubMatrix(M1,M2,atemp,half);
      AddMatrix(M3,M6,btemp,half);
      AddMatrix(atemp,btemp,c22,half);
      //printf("---> Strassen Task: Printing c22\n");
      //printMatrix(c22, half);
      
      /* Now fill in C */
      int j = 0;
      for(i = 0 ; i < half ; i++) {
	for(j = 0 ; j < half ; j++) {
	  _c[i*mysize + j] = c11[i*half + j];
	  _c[(i+half)*mysize + j] = c12[i*half + j];
	  _c[i*mysize + (j+half)] = c21[i*half + j];
	  _c[(i + half)*mysize + (j + half)] = c22[i*half + j];
	}
      }
      //printf("----> Strassen Task: Results returned and result filled\n");
      /* Free Everything */
      free(a11);
      free(a12);
      free(a21);
      free(a22); 
      free(b11);
      free(b12);
      free(b21);
      free(b22);
      free(c11);
      free(c12);
      free(c21);
      free(c22); 
      free(M1);
      free(M2);
      free(M3);
      free(M4);
      free(M5);
      free(M6);
      free(M7);
      free(atemp);
      free(btemp);
      *_pState = Done;
      *doneState = Done;
    }
    else {// at least one task is idle, put myself back in queue and wait
      //printf("----> Strassen Task: I should now go back to sleep \n");
      //Q->addTask(this);
      //printf("----> Put back to sleep by thread %d\n", pthread_self());
      //printf("----> Strassen Task: I added myself to the queue\n");
      *_pState = Idle;
      if (pthread_yield() != 0) {
	//printf("----> Strassen Task: Thread yeilding error \n");
	//printf("%s", strerror(errno));
      	pthread_exit(0);
      }
    }
  }
}

void Strassen::print() {
	printf("I am Strassen task  %d printing\n", sid);
}
/* End of Strassen Task */

